Francesco Pirotti
2024-09-05
Trovate il materiale in una pagina GitHub QUIe potrete scaricarlo anche come zip ed anche direttamente nello spazio web QUI. Troviamo:
Verrà fornito un link GDrive per caricare gli esercizi per le verifiche.
R è un programma basato su righe di codice con comandi che eseguono elaborazioni su dati.
L’utente immette i comandi al prompt ( > ) e ciascun comando viene eseguito uno alla volta andando a capo.
Le righe di comando solitamente vengono salvate in un file “script” con estensione “R” (.R) e vengono eseguite una alla volta mediante “invio” o con selezione multipla e “invio”.
Con RStudio è possibile eseguire l’intero file, fermandosi eventualmente in punti specifici “breakpoints” (lo vedremo durante il corso).
Esercizio: esegui comando della figura alla slide precedente.
## [1] "ciao"
Se non si vuole eseguire delle righe, basta mettere un carattere
asterisco (#) all’inizio del testo che NON si vuole
eseguire.
NB se inserito all’inizio della riga, e seguito da testo e da 4 o più
caratteri - o #, il testo diventa una
sezione
Ogni singola funzione ha ampia documentazione con molti esempi. Chiamando una funzione dopo uno o due punti interrogativi richiama la documentazione. Quasi sempre gli esempi sono eseguibili facendo copia/incolla
Esercizio: esegui il primo esempio dalla documentazione della funzione print
Tantissime funzionalità aggiuntive sono disponibili su componenti
aggiuntivi che vanno installate con il comando
install.packages(<nome libreria>) e poi caricate con
il comando library(<nome libreria>). Da RStudio
possiamo caricarle da interfaccia grafica.
Non tutti sono nella “repository” CRAN, alcuni sono su GitHub o scaricabili da altre fonti - vedi alcune cose sotto
Per installare librarie presenti solo in CRAN si utilizza una libreria chiamata “devtools”
Qui vediamo una variabile ed una funzione.
NB1 - la variabile “saluto” è nel Environment (“ambito/ambiente/campo”) globale*. La funzione “print” è nel campo del package “base” - tieni premuto il tasto CTRL e seleziona il nome della funzione - vedi cosa succede.
NB2 - operatore di assegnazione <-
(o <<- nel caso si voglia forzare l’assegnazione ad
una variabile globale*)
*Le variabili create al di fuori di funzioni sono note come variabili globali; possono essere utilizzate sia all’interno delle funzioni che all’esterno.
Sotto andiamo a creare una nostra funzione “salutami” che esegue il
saluto. Provate a modificare l’operatore di assegnazione da
<<- a <<- e rieseguire!!
## [1] "ciao"
## [1] "ciao ARPA!!!"
## [1] "ciao"
NB ogni elemento in R è considerato (ed è) un VETTORE. Le funzioni di R considerano ogni variabile un vettore. Cosa significa? Che le funzioni elaborano tutti gli elementi di un vettore “by default” e che ogni elemento è indicabile con un numero iniziando da 1 (non da 0 come solitamente succede in altri linguaggi).
Perchè succede quello che vedete sotto (dati numeric diventano character)?
## [1] "numeric"
## [1] 1
## [1] 4
## [1] "character"
## [1] "1" "evviva" "6" "8"
Matrix è un oggetto con struttura di matrice ovvero bidimensionale (righe × colonne); pensate a un gruppo di vettori impilati o affiancati.
Si accede e si assegnano i valori con [r,c] dove r e ci sono gli indici di riga e colonna. Si può lasciare vuoto un indice per accedere alla riga/colonna
## [1] 2
## [1] 3
## [1] 1 3
Un array è una matrice multidimensionale.
r=rows, c=columns, m=matrice…. etc…
Esercizio: vedi sotto come creare un array a 3 dimensioni. Nota che duplica 9 valori 2 volte. Prova a dare 8 valori invece che nove. Prova a dare 3 valori. Cosa succede.
# 2 vettori di valori
valori1 <- c(5, 9, 3)
valori2 <- c(10, 11, 12, 13, 14, 15)
column.names <- c("C1", "C2", "C3")
row.names <- c("R1", "R2", "R3")
matrix.names <- c("Matrix1", "Matrix2")
# Crea un array a tre dimensioni
arr <- array(c(valori1, valori2), dim = c(3, 3, 2),
dimnames = list(row.names,
column.names,
matrix.names))
print(arr) ## , , Matrix1
##
## C1 C2 C3
## R1 5 10 13
## R2 9 11 14
## R3 3 12 15
##
## , , Matrix2
##
## C1 C2 C3
## R1 5 10 13
## R2 9 11 14
## R3 3 12 15
Le strutture vector/matrix/array, possono contenere solo una tipologia base (numeric, integer, character, logical…). Ma la struttura LIST no!
La struttura list è un set di dati eterogenei; opzionalmente, è possibile assegnare dei nomi a ciascun elemento nel set.
## [1] "list"
## [1] 1
## [1] 4
## [1] "list"
## [1] 1
## [1] "evviva"
## [[1]]
## [1] 1
##
## [[2]]
## [1] "evviva"
##
## [[3]]
## [1] 6
##
## [[4]]
## [1] 8
NB, la struttura LIST non è altro che un set, una “lista”, di oggetti associata ad un indice. L’indice è un numero intero ma può essere un testo (simile al concetto di coppie “key->value”/chiave->valore).
Può essere assegnato un indice/chiave per riferimento all’elemento nella lista
## NULL
## [1] 1
## [1] 1
Svantaggi di strutture tipo list: usa più memoria!
Nota su allocazione memoria: se dovete gestire volumi importanti di dati, considerate la pre-allocazione della memoria SE conoscete la dimensione. Vedi anche blog qui.
## [1] 0
Un data frame è una struttura di tipo list ma con un numero uguale di “righe” per ogni colonna di attributi. È possibile manipolare i data frame filtrando sulle righe e operando sulle colonne.
Sia righe che colonne possono avere identificativi.
c.lat <- c(45.1, 45.2, 45.3)
c.lon <- c(11.1, 11.2, 11.3)
df.geo.stz <- data.frame(stz=c("A", "B","C"),
longitudine=c.lon,
latitudine=c.lat)
colnames(df.geo.stz)## [1] "stz" "longitudine" "latitudine"
## [1] "1" "2" "3"
## [1] "A" "B" "C"
Le colonne sono vettori - si può richiamare e assegnare i
valori di una colonna con $ o [[]] o
[,"<nomecolonna>"] o
[ riga,"<nomecolonna>"]
Come con Tipo dati: matrix si possono usare gli indici di riga/colonna.
Un data frame particolare, lo vedremo quando usiamo l’infrastruttura di librerie “tidyverse”. Vantaggi:
Non modifica il nome mettendo un punto al posto degli spazi (Vedi esempio)
Non utilizza mai row.names(). Lo scopo di
tidyverse è quello di memorizzare le variabili in modo
coerente. Quindi non memorizza mai una variabile come attributo speciale
(i.e. nomi riga).
## [1] "tbl_df" "tbl" "data.frame"
## [1] "nome.colonna.con.spazi"
## [1] "nome colonna con spazi"
## [1] 1 2 3 4 5 6 7 8 9 10
## [1] 1 1 1 1 1 1 1 1 1 1
## [1] 1 2 3 4 5 1 2 3 4 5
Oggetti tra loro compatibili si possono convertire; ad esempio tra
matrix => data.frame usando il comando
as.data.frame(<oggetto matrix>) viceversa attenzione
che tutti i tipi di vettore vengono resi omogenei.
Provate il seguente esercizio sotto.
## [,1] [,2]
## [1,] 1 100
## [2,] 2 4
## V1 V2
## [1,] 1 100
## [2,] 2 4
## V1 V2 nuovaColonna
## [1,] "1" "100" "testo"
## [2,] "2" " 4" "testo"
## V1 V2 nuovaColonna
## 1 1 100 testo
## 2 2 4 testo
Rstudio può salvare l’intero progetto con le variabili e le funzioni che vedete in alto a destra.
Il comando save salva in un file con estensione “rda”
che viene riconosciuto anche direttamente da RStudio. NB è possibile
salvare qualiasi oggetto, anche funzioni!
Prova a cliccare sul file dopo aver lanciato la prima riga del comando seguente!
Gli operatori aritmetici e logici di R funzionano sia su singoli scalari che su vettori e strutture come array e matrix.
NB questo vuol dire che si possono eseguire operazioni su tutti i singoli valori della struttura internamente (vectorization)!
Impareremo a leggere/trasformare/scrivere dati da fonti diverse:
file di testo strutturati (CSV)
fogli di lavoro (XLSX)
dati online non strutturati come JSON
I file CSV sono notoriamente file di testo strutturati come tabelle e molto utilizzati per condividere dati.
Il comando standard è read.csv .
NB non è ottimizzato per l’efficienza. Per dati massivi usare il
comando fread dalla libreria data.table
data.table::fread().
Facciamo un esercizio con dati ARPA! Scarichiamo dalla pagina QUI
o direttamente da QUI
i dati di precipitazione mensile - salvateli in una sottocartella “dati”
e caricateli usando sia la funzione read.csv che fread dalla libreria
data.table (se l’avete altrimenti vedete a schermo i risultati). Si
vuole notare le differenze e come si chiama una funzione SENZA caricare
una libreria, usando ::.
prec <- read.csv("dati/Prec_mensili_2020.csv")
prec2 <- data.table::fread("dati/Prec_mensili_2020.csv")
summary(prec2)## Precipitazioni mensili Veneto 2020 (mm) media 1993-2019 (mm)
## Length:13 Min. : 7.0 Min. : 57.0
## Class :character 1st Qu.: 28.0 1st Qu.: 74.0
## Mode :character Median : 89.0 Median : 96.0
## Mean : 180.2 Mean : 171.5
## 3rd Qu.: 171.0 3rd Qu.: 114.0
## Max. :1171.0 Max. :1114.0
Esercizio: Importate i dati inserendo i parametri corretti di read.csv - come noterete, la prima riga NON importa correttamente. La seconda si, ma è una funzione della libreria data.table. Usate la documentazione in R per capire come aggiungere argomenti alla funzione read.csv (vedi sezione QUI). NB usate il tasto TAB per richiamare i percorsi, risparmiate tempo e fatica!
I fogli di calcolo in formato MS Excel sono più strutturati rispetto ad un file CSV, dato che ogni colonna ha anche lo specifico tipo di dati (numero, testo, data ecc…).
Per leggere/scrivere questi dati è necessario usare librerie aggiuntive.
prec3 <- prec2
## nuova colonna
prec3$norm2020 <- prec3$`2020 (mm)` / prec3$`media 1993-2019 (mm)`
writexl::write_xlsx( list("originale" =prec2, "elaborata"=prec3), path="dati/output.xlsx" )
prec4 <- readxl::read_xlsx("dati/output.xlsx")La funzione plot riesce a creare grafici di base.
Andiamo a confrontare le precipitazioni del 2020 con quelle medie
1993-2019.
plot(prec3$`2020 (mm)`[1:12], prec3$`media 1993-2019 (mm)`[1:12], xlab="Prec. 2020 (mm)")
abline(1,1)Miglioriamo un po’ il grafico - ma utilizzeremo poi la libreria ggplot2 per ottenere grafici migliori.
plot(prec3$`2020 (mm)`[1:12], prec3$`media 1993-2019 (mm)`[1:12],
xlab="Prec. 2020 (mm)", ylab="Prec.media (mm)",
xlim=c(0,300), ylim=c(0,300))
abline(1,1)I dati JSON (JavaScript Object Notation) sono molto utilizzati per trasferire dati non strutturati. Vedi QUI e QUI. Anche per dati “geo” con il GeoJSON (vedi esempio QUI).
Usiamo direttamente da API ARPAV “Anagrafica stazioni in formato JSON” dalla pagina QUI
Modificare dati significa aggiungere, togliere, filtrare, manipolare colonne, creare sommari e statistiche di interesse aggregando dati. Insomma il dato viene “manipolato”. In inglese viene anche usato “data wrangling”
Ci “alleniamo” con i dati JSON delle stazioni meteo ARPAV scaricati dal JSON.
Obiettivo: creare un grafico con la posizione delle stazioni e aggiungere qualche valore mediante unione con altri dati.
Fondamentale saper utilizzare operazioni che eseguono su tutti gli elementi di un oggetto. E.g. tutte le colonne di un data.frame, tutte le righe di un data.frame, tutti gli oggetti di una list…
dati <- stazioni.meteo$data
length(dati)
## LOOP classico: visto male per
## performance bassa,
## ma non necessariamente vero
for(i in 1:length(dati)){
print(dati[[i]])
break # esce dal ciclo
next # continua il ciclo senza elaborare le righe successive
print("Questa riga non viene elaborata")
}
funz <- function(x){
length(x)
}
# lapply/mapply/sapply/apply per righe/colonne
# togliete il commento alla riga sotto
# lapply(dati, funz) Tidyverse, dal sito: “un set di funzioni e librerie di R per la data science. Tutti i pacchetti condividono una filosofia di progettazione, una grammatica e strutture di dati”.
La grammatica per la manipolazione dei dati fornisce un insieme coerente di funzioni (verbi) dalla libreria dplyr:
mutate() aggiunge nuove variabili
che sono funzioni di variabili esistenti
select() seleziona le variabili in
base al loro nome.
filter() seleziona i casi in base
ai loro valori.
summarise() riduce più valori a un
singolo riassunto.
arrange() cambia l’ordine delle
righe.
Combinando con group_by() questa
operazione viene stratificata “per gruppo” aggregando le operazioni. Ci
sono molti altri operatori ma questi sono quelli di base.
L’operatore “map” è simile agli “apply” ma con più funzionalità. Usando map_df i risultati vengono raccolti su una tabella (tibble)!
Mappando a data.frame con map abbiamo colonne di tipo
character che dovrebbero essere numeric dobbiamo in alcuni casi
assegnare il tipo alla colonna come sotto.
NB gli iteratori su tabelle (data.frame o tibble) lavorano per colonne (column-wise).
NOTA che map raccoglie ogni elemento della lista in un vettore ed il vettore può avere solo valori di un tipo base (character, numeric, integer, logical…) - sceglie sempre il character se trova diverse tipologie.
NB con map_df questo non succede perchè la funzione
map_df è “smart” e capisce qualche tipo usare per ogni
colonna. TRANNE che per la colonna valore. Perché?**
** Nota che nel JSON il numero in “valore” è tra virgolette, dunque viene visto come testo (character) e non come numerico. Confronta con latitudine e longitudine!
## List of 15
## $ codice_stazione: int 19
## $ codseqst : int 300000170
## $ nome_stazione : chr "Agordo"
## $ point : chr "{\"type\": \"Point\", \"coordinates\": [12.03319083, 46.27804651]}"
## $ longitudine : num 12
## $ latitudine : num 46.3
## $ quota : num 585
## $ nome_sensore : chr "Temperatura aria a 2m"
## $ dataora : chr "2024-09-05T19:00:00"
## $ valore : chr "16.0"
## $ misura : chr "°C"
## $ tipo : chr "TARIA2M"
## $ aggiornamento : chr "2024-09-05T19:15:06"
## $ gestore : chr "ARPAV"
## $ provincia : chr "BELLUNO"
Operazioni usando tidyverse (e altri sistemi) usano operatori di
concatenzione di processi. In questo caso si usa %>%
ovvero un operatore che indica di usare l’output di un processo come
input al processo successivo. Un esempio con un’operazione di filtro con
successiva selezione di colonne:
## Selezioniamo solo le stazioni in provincia di Belluno
dtf.belluno <- dtf %>% filter( provincia =="BELLUNO")
## Se aggiungiamo criteri di filtro divisi da virgola è come usare AND
dtf %>% filter( provincia !="BELLUNO", valore < 10)
dtf %>% filter( provincia !="BELLUNO" & valore < 10)
## Se aggiungiamo criteri di filtro è come usare AND
dtf %>% filter( provincia =="BELLUNO" | valore < 12) %>% select(nome_stazione, valore)Il comando mutate consente di calcolare nuove colonne in funzione di quelle esistenti.
Operazioni di aggregazione con group_by e
summarise
Attenzione - la funzione mean restituisce
NA se tra i valori trova degli NA, per evitare
questo comportamento mettere “na.rm = TRUE”.
## # A tibble: 3 × 2
## tipo valore
## <fct> <dbl>
## 1 TARIA2M NA
## 2 TARIA5M NA
## 3 TARIA8M 9.66
## # A tibble: 3 × 2
## tipo valore
## <fct> <dbl>
## 1 TARIA2M 17.1
## 2 TARIA5M 8.88
## 3 TARIA8M 9.66
I join sono operazioni note in ambito di gestione dei dati, servono per aggiungere in senso orizzontale valori usando una o più colonne con valori corrispondenti.
Vediamo un esempio pratico: andiamo ad unire i valori di oggi a quelli di alcuni giorni fa alle nostre stazioni. Scarica qui il dato CSV.
Esportare un foglio excel ed un file CSV separato da TAB con i dati di precipitazione in Veneto fatti fino a questo punto (df.joined) ed un file di R che li legge in 2 oggetti tibble.
consegnate: i seguenti file:
es2_cognome_nome.xlsx
es2_cognome_nome.csv
es2_cognome_nome.R
Questo tipo di modifica è molto comune per rappresentare i dati in modo differente. Vediamo un esempio pratico:
## andiamo a creare classi di temperatura
classi <- cut(dtf$valore, breaks = c(-10,0,10,20,30))
## mappatura con tabella a doppia entrata
tb.wide <- table(dtf$provincia, classi)
## nota che è un oggetto "table", trasformiomo in data.frame
tb.wide <- as.data.frame.matrix(tb.wide)
rownames(tb.wide)## [1] "BELLUNO" "PADOVA" "ROVIGO" "TREVISO" "UDINE" "VENEZIA" "VERONA"
## [8] "VICENZA"
## nota ancora che le provincie sono rownames - se converto
## a tibble non ci sono pià
rownames(as_tibble(tb.wide) )## [1] "1" "2" "3" "4" "5" "6" "7" "8"
## creo colonna e relacate la mette all'inizio (provate senza "relocate")
tb.wide <- tb.wide %>% mutate(Provincie = rownames(tb.wide))%>% relocate(Provincie)
## tabella con numero di stazioni per classe espressa "wide"
tb.wide## Provincie (-10,0] (0,10] (10,20] (20,30]
## BELLUNO BELLUNO 0 17 34 0
## PADOVA PADOVA 0 0 8 2
## ROVIGO ROVIGO 0 0 4 2
## TREVISO TREVISO 0 0 13 2
## UDINE UDINE 0 0 1 0
## VENEZIA VENEZIA 0 0 10 5
## VERONA VERONA 0 0 8 5
## VICENZA VICENZA 0 1 32 2
## trasformo
tb.long <- tb.wide %>% pivot_longer(!Provincie,
names_to = "Classe Temp. °C", values_to = "Numero"
)
tb.long## # A tibble: 32 × 3
## Provincie `Classe Temp. °C` Numero
## <chr> <chr> <int>
## 1 BELLUNO (-10,0] 0
## 2 BELLUNO (0,10] 17
## 3 BELLUNO (10,20] 34
## 4 BELLUNO (20,30] 0
## 5 PADOVA (-10,0] 0
## 6 PADOVA (0,10] 0
## 7 PADOVA (10,20] 8
## 8 PADOVA (20,30] 2
## 9 ROVIGO (-10,0] 0
## 10 ROVIGO (0,10] 0
## # ℹ 22 more rows
Operazione inversa per tornare alla tabella di origine
tb.long2wide <- tb.long %>% pivot_wider(
names_from = "Classe Temp. °C", values_from = "Numero"
)
tb.long2wide## # A tibble: 8 × 5
## Provincie `(-10,0]` `(0,10]` `(10,20]` `(20,30]`
## <chr> <int> <int> <int> <int>
## 1 BELLUNO 0 17 34 0
## 2 PADOVA 0 0 8 2
## 3 ROVIGO 0 0 4 2
## 4 TREVISO 0 0 13 2
## 5 UDINE 0 0 1 0
## 6 VENEZIA 0 0 10 5
## 7 VERONA 0 0 8 5
## 8 VICENZA 0 1 32 2
Le funzioni di base “plot” sono utili ma ci sono
librerie che forniscono più funzionalità. Introduciamo “ggplot”. Notate
che viene creato un grafico concatenando con l’operatore
“+” gli oggetti del grafico. “aes” sta per
aestethics e indica colori e valori degli assi, aggregazioni
ecc…
library(ggplot2)
ggplot(dtf, aes(x=quota, y=as.numeric(valore))) + geom_point( ) +
geom_smooth(method = "lm")+ theme_bw()NB. la parte “as.numeric” in quanto la colonna anche contiene numeri è di tipo “character”. Modifichiamola con “as.numeric”, ma vedrete due valori mancanti (NA=not available).
dtf$valore <- as.numeric(dtf$valore)
ggplot(dtf) + geom_boxplot(aes(x=tipo, y=valore )) + theme_bw()facet_wrap e facet_grid consentono di
“esplodere” per una o più variabili di raggruppamento. Qui un esempio
con una stima della funzione di distribuzione delle frequenze.
facet_wrap e facet_grid consentono di
“esplodere” per una o più variabili di raggruppamento.
Abbiamo colonne con coordinate: possiamo creare un grafico con uno sfondo territoriale e considerare delle colonne come coordinate? Per dati geo usiamo libreria sf e terra. Non indispensabili per geostatistica, ma fortemente consigliati.
library(ggspatial, quietly = T)
library(sf) ## se non l'avete installata non vi preoccupate
theme_set(theme_bw())
ggplot(dtf) + geom_point(aes(x=longitudine, y=latitudine, color=valore, size=valore )) dtf.geo <- sf::st_as_sf(dtf,coords = c("longitudine","latitudine"), crs=4326 )
ggplot(dtf.geo) + geom_sf(aes(color=valore, size=valore )) + coord_sf()Il metodo per avere grafici su stampa mediante immagini o pdf
png("fileGrafico.png")
p <- ggplot(dtf.geo) + geom_sf(aes(color=valore, size=valore )) + coord_sf()
print(p)
dev.off()possiamo impostare dimensioni in pixel (width e height) ed inoltre la risoluzione (res)… provate a vedere.
Il formato PDF ha il vantaggio/svantaggio di essere vettoriale (non perde definizione) - provate a vedere la definizione. Perchè non usiamo SEMPRE pdf? Immaginatevi a inserire milioni di punti… perde efficacia.
pdf("fileGrafico.pdf")
p <- ggplot(dtf.geo) + geom_sf(aes(color=valore, size=valore )) + coord_sf()
print(p)
dev.off()NB possiamo creare un iterazione per fare un grafico per foglio in un singolo file PDF!
plotMe <- function(tipoStazione){
dati <- dtf.geo %>% filter(tipo==tipoStazione)
p <- ggplot(dati) + geom_sf(aes(color=valore)) + ggtitle(paste("Tipo: ", tipoStazione)) + coord_sf()
p
}
## NB la colonna "tipo" è di tipo FACTOR
pdf("fileGraficoMulti.pdf")
for(tipoStz in levels(dtf.geo$tipo) ){
p <- plotMe(tipoStz)
print(p)
}
dev.off()Vedi file “modulo1_03a_esercizio_finale2.R” e “modulo1_03b_esercizio_finale2.R” nella cartella “esercizi/modulo1” - eseguirete il seguente grafico estraendo i dati dalle API meteo ARPAV.
A grandi linee i dati spaziali sono interpretati digitalmente mediante due modelli: il modello vettoriale ed il modello raster. In entrambi i casi va comunque compreso che ogni elemento è georiferito, ovvero ha una localizzazione in un sistema di riferimento, quindi delle coordinate.
I dati vettoriali hanno tre primitive geometriche di base: punti linee e poligoni. La base è sempre il punto con una coordinate, in 2d o 3d.
Ci sono decine di formati di file che rappresentano dati vettoriali. Ne abbiamo visto uno all’inizio, il file JSON! Per un riferimento vedete la pagina QUI. Ad oggi la libreria di riferimento per leggere questi dati è la libreria sf.
Il formato raster è molto semplice: sono nodi a passo costante, quindi una “griglia” georiferita mediante uno o più punti. Il passo della griglia è spesso identico nelle due direzioni (ma non necessariamente), e ne determina la risoluzione spaziale.
Ci sono decine di formati di file che rappresentano dati raster. Per un riferimento vedete la pagina QUI. Ad oggi la libreria di riferimento per leggere questi dati è la libreria terra.
Stimare valori di una variabile applicando un modello creato conoscendo valori misurati (e.g. campionamenti). E’ ragionevole adottare un criterio spaziale, ovvero usare un criterio di spazio (il valore del punto sarà probabilmente più simile ad una misura vicina che una misura più lontana, a parità di altre condizioni). Da qui la geostatistica.